#!/bin/ksh
# Copyright (C) 2019 Checkmk GmbH - License: GNU General Public License v2
# This file is part of Checkmk (https://checkmk.com). It is subject to the terms and
# conditions defined in the file COPYING, which is part of this source code package.

# Author: Lars Michelsen <lm@mathias-kettner.de>
#         Florian Heigl <florian.heigl@gmail.com>
#	  Christian Zigotzky <chzigotzky@xenosoft.de>

# NOTE: This agent has been adapted from the Checkmk FreeBSD agent.

inpath() {
    # replace "if type [somecmd]" idiom
    # 'command -v' tends to be more robust vs 'which' and 'type' based tests
    command -v "${1:?No command to test}" >/dev/null 2>&1
}

get_epoch() {
    date +%s 2>/dev/null ||
        perl -e 'print($^T."\n");'
}

# Remove locale settings to eliminate localized outputs where possible
export LC_ALL=C
unset LANG

export MK_LIBDIR="/usr/lib/check_mk_agent"
export MK_CONFDIR="/etc"

# Optionally set a tempdir for all subsequent calls
#export TMPDIR=

# Make sure, locally installed binaries are found
PATH=$PATH:/usr/local/bin

# All executables in PLUGINSDIR will simply be executed and their
# ouput appended to the output of the agent. Plugins define their own
# sections and must output headers with '<<<' and '>>>'
PLUGINSDIR=$MK_LIBDIR/plugins

# All executables in LOCALDIR will by executabled and their
# output inserted into the section <<<local>>>. Please refer
# to online documentation for details.
LOCALDIR=$MK_LIBDIR/local

if inpath sudo && [ "$(whoami)" != "root" ]; then
    ROOT_OR_SUDO="sudo --non-interactive"
else
    ROOT_OR_SUDO=""
fi
export ROOT_OR_SUDO

# close standard input (for security reasons) and stderr
if [ "$1" = -d ]; then
    set -xv
else
    exec </dev/null 2>/dev/null
fi

_free_mem_index_vmstat() {
    # The number of output columns in the vmstat command changed in newer versions. We want the
    # entry under 'fre' (not a typo).
    # https://man.openbsd.org/OpenBSD-6.0/vmstat.8
    # https://man.openbsd.org/vmstat.8
    idx=1
    for col in "$@"; do
        if [ "$col" = "fre" ]; then
            printf "%s" "$idx"
            return 0
        fi
        idx=$((idx + 1))
    done
    return 1
}

_free_mem_with_unit_vmstat() {
    # Newer vmstat versions print "1M" instead of "1024" as free memory
    # SUP-11138
    # https://github.com/openbsd/src/blob/1c702c4a4395025332d4340f326500c590811dc8/usr.bin/vmstat/vmstat.c#L368
    case "${1}" in
        *M)
            mem_free_mb_with_unit="${1}"
            printf "%s" "${mem_free_mb_with_unit%?} MB"
            ;;
        *)
            printf "%s" "${1} kB"
            ;;
    esac
}

_free_mem_with_unit() {
    vmstat_output=$(vmstat)

    # shellcheck disable=SC2046  # we want word splitting here
    if ! mem_free_idx="$(_free_mem_index_vmstat $(echo "$vmstat_output" | tail -n2 | head -1))"; then
        return 1
    fi

    _free_mem_with_unit_vmstat "$(echo "$vmstat_output" | tail -n1 | awk -v idx="$mem_free_idx" '{ print $idx }')"

    return 0
}

section_openbsd_mem() {
    if ! mem_free_with_unit=$(_free_mem_with_unit); then
        return 1
    fi
    mem_total=$(sysctl hw.usermem | cut -d= -f2)
    mem_total=$(echo "$mem_total/1024" | bc)

    swapctl_output=$(swapctl -k -s)
    swap_free=$(echo "$swapctl_output" | awk '{ print $7 }')
    swap_total=$(echo "$swapctl_output" | awk '{ print $2 }')

    # if there is no swap space swap values are 0
    if [ -z "$swapctl_output" ]; then
        swap_free=0
        swap_total=0
    fi

    printf "<<<openbsd_mem>>>\n"
    printf "MemTotal:\t %s kB\n" "$mem_total"
    printf "MemFree:\t %s\n" "$mem_free_with_unit"
    printf "SwapTotal:\t %s kB\n" "$swap_total"
    printf "SwapFree:\t %s kB\n" "$swap_free"

    return 0
}

section_misc_sections() {
    echo "<<<check_mk>>>"
    echo "Version: 2.5.0b1"
    echo "AgentOS: openbsd"
    echo "Hostname: $(hostname)"
    echo "AgentDirectory: $MK_CONFDIR"
    echo "DataDirectory: $MK_VARDIR"
    echo "SpoolDirectory: $SPOOLDIR"
    echo "PluginsDirectory: $PLUGINSDIR"
    echo "LocalDirectory: $LOCALDIR"
    echo "OSType: unix"
    echo "OSName: $(uname -s)"
    echo "OSVersion: $(uname -r)"

    echo '<<<df>>>'
    df -kPt ffs | sed -e 's/^\([^ ][^ ]*\) \(.*\)$/\1 ffs \2/' | sed 1d

    # processes including username, without kernel processes
    echo '<<<ps>>>'
    echo "[time]"
    get_epoch
    echo "[processes]"
    ps ax -ww -o user,vsz,rss,pcpu,command | sed -e 1d -e 's/ *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) *\([^ ]*\) */(\1,\2,\3,\4) /'

    echo '<<<cpu>>>'
    echo "$(sysctl -n vm.loadavg | tr -d '{}') $(top -b -n 1 | grep -E '^[0-9]+ processes' | awk '{ print $3"/"$1 }') $(sysctl -n hw.ncpu)"

    echo '<<<uptime>>>'
    echo "$(get_epoch) - $(sysctl -n kern.boottime | cut -d' ' -f 4,7 | tr ',' '.' | tr -d ' ')" | bc

    section_openbsd_mem

    echo '<<<lnx_if:sep(58)>>>'
    # MC= MAC address
    # BI= Bytes in
    # PI= Packets in
    # EI= Errors in
    # EO= Errors out
    # BO= Bytes out
    # PO= Packets out
    # CO= Colls
    # NI= Number of interfaces
    # INTERFACES= Array of interfaces

    set -A INTERFACES
    set -A MC
    set -A BI
    set -A BO
    set -A PI
    set -A PO
    set -A EI
    set -A EO
    set -A CO

    NI=0
    # special (lo/pfsync/pflog/enc) and inactive (*) interfaces are not needed
    NETSTAT_OUTPUT=$(netstat -in | grep '<Link>' | grep -v -E "\*|lo|pfsync|enc")
    NETSTAT_OUTPUT_BYTES=$(netstat -inb | grep '<Link>' | grep -v -E "\*|lo|pfsync|enc")

    # adjust internal field separator to get lines from netstat and backup it before
    OFS=$IFS
    IFS=''

    # collect netstat values and interface number
    for NS in $NETSTAT_OUTPUT; do
        NI=$((NI + 1))
        INTERFACES[NI]=$(echo "$NS" | awk '{ print $1 }')
        MC[NI]=$(echo "$NS" | awk '{ print $4 }')
        PI[NI]=$(echo "$NS" | awk '{ print $5 }')
        EI[NI]=$(echo "$NS" | awk '{ print $6 }')
        PO[NI]=$(echo "$NS" | awk '{ print $7 }')
        EO[NI]=$(echo "$NS" | awk '{ print $8 }')
        CO[NI]=$(echo "$NS" | awk '{ print $9 }')
    done

    # need NIC counter again for byte values - reset it
    NI=0
    for NS in $NETSTAT_OUTPUT_BYTES; do
        NI=$((NI + 1))
        BI[NI]=$(echo "$NS" | awk '{ print $5 }')
        BO[NI]=$(echo "$NS" | awk '{ print $6 }')
    done

    # what is this for?
    [ "${NI}" -ge 1 ] || NI=15

    # jot is OpenBSD "range"
    for i in $(jot $NI); do
        echo "${INTERFACES[$i]}:${BI[$i]} ${PI[$i]} ${EI[$i]} 0 0 0 0 0 ${BO[$i]} ${PO[$i]} ${EO[$i]} 0 0 ${CO[$i]} 0 0"
    done

    for IF in $(jot $NI); do
        echo "[${INTERFACES[$IF]}]"

        IFCONFIG_OUTPUT=$(ifconfig "${INTERFACES[$IF]}")
        for IO in $IFCONFIG_OUTPUT; do
            # Speed
            SP=$(echo "$IO" | grep -E "media:.*base" | cut -d\( -f2 | cut -db -f1)
            if [ "$SP" ]; then
                case "$SP" in
                    *G*) SP=$((${SP%G} * 1000)) ;;
                esac
                printf "\tSpeed: %sMb/s\n" "${SP}"
            fi
            # Detect duplexity - in reality only available for physical devices but
            # virtual ones like CARP devices will get at least a half duplex
            if echo "$IO" | grep -q -E "media:.*full-duplex"; then
                printf "\tDuplex: Full\n"
            elif echo "$IO" | grep "media:" | grep -q -v "full-duplex"; then
                printf "\tDuplex: Half\n"
            fi
            # Auto-negotiation
            if echo "$IO" | grep -q -E "media:.*autoselect"; then
                printf "\tAuto-negotiation: on\n"
            elif echo "$IO" | grep "media:" | grep -q -v "autoselect"; then
                printf "\tAuto-negotiation: off\n"
            fi
            # Detect detected link
            if echo "$IO" | grep "status:" | grep -q -E "active|backup|master"; then
                printf "\tLink detected: yes\n"
            fi
        done

        printf "\tAddress: %s\n" "${MC[$IF]}"

    done

    # reset IFS to default
    IFS=$OFS

    # IPMI-Data (Fans, CPU, temperature, etc)
    # needs the sysutils/ipmitool or freeipmi and kldload ipmi.ko
    if inpath ipmitool; then
        echo '<<<ipmi>>>'
        ipmitool sensor list |
            grep -v 'command failed' |
            sed -e 's/ *| */|/g' -e "s/ /_/g" -e 's/_*$//' -e 's/|/ /g' |
            grep -v -E '^[^ ]+ na ' |
            grep -v ' discrete '
    elif inpath ipmi-sensors; then
        echo '<<<ipmi_sensors>>>'
        if ipmi-sensors --help | grep -q legacy-output; then
            IPMI_FORMAT="--legacy-output"
        else
            IPMI_FORMAT=""
        fi
        if ipmi-sensors --help | grep -q " \-\-groups"; then
            IPMI_GROUP_OPT="-g"
        else
            IPMI_GROUP_OPT="-t"
        fi
        for class in Temperature Power_Unit Fan; do
            ipmi-sensors ${IPMI_FORMAT} --sdr-cache-directory /var/cache ${IPMI_GROUP_OPT} "${class}" | sed -e 's/ /_/g' -e 's/:_/ /g' -e 's@ \([^(]*\)_(\([^)]*\))@ \2_\1@'
            # In case of a timeout immediately leave loop.
            if [ $? = 255 ]; then break; fi
        done
    fi

    if inpath mailq && getent passwd postfix >/dev/null 2>&1; then
        echo '<<<postfix_mailq>>>'
        ${ROOT_OR_SUDO} mailq | tail -n 6
    fi
}

run_plugins() {
    (
        cd "${PLUGINSDIR}" || return
        for script in "./"*; do
            [ -x "${script}" ] && "${script}"
        done
    )
}

run_local_checks() {
    (
        cd "${LOCALDIR}" || return
        echo '<<<local:sep(0)>>>'
        for script in "./"*; do
            [ -x "${script}" ] && "${script}"
        done
    )
}

# TODO: sync with linux agent
run_mrpe_plugins() {
    if [ -e "$MK_CONFDIR/mrpe.cfg" ]; then
        echo '<<<mrpe>>>'
        # SC2162: read without -r will mangle backslashes.
        # We suppress it here for compatibility (curretly backslashes e.g. before spaces are dropped).
        # Since escaping of field seperators is not relevant when reading into one variable, we probably
        # would have wanted "read -r".
        # shellcheck disable=SC2162
        grep -Ev '^[[:space:]]*($|#)' "$MK_CONFDIR/mrpe.cfg" |
            while read descr cmdline; do
                PLUGIN=${cmdline%% *}
                OUTPUT=$(eval "$cmdline")
                printf "(%s) %s %s %s" "${PLUGIN##*/}" "$descr" "$?" "$OUTPUT" | tr \\n \\1
                echo
            done
    fi
}

main() {

    section_misc_sections

    run_plugins

    run_local_checks

    run_mrpe_plugins

    return 0
}

[ -n "${MK_SOURCE_AGENT}" ] || main "$@"
